﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using MugenMvvmToolkit.Infrastructure;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.Utils;
using MugenMvvmToolkitTest.TestInfrastructure;
using MugenMvvmToolkitTest.TestModels;
using Should;

namespace MugenMvvmToolkitTest.Infrastructure
{
    //TODO REWRITE TESTS
    [TestClass]
    public class ValidatorBaseTest : TestBase
    {
        #region Fields

        public const string ValidatorError = "validatorError";

        public const string PropertyToValidate = "PropertyToValidate";
        public const string PropertyToMap = "PropertyToMap";

        #endregion

        #region Test methods

        [TestMethod]
        public void NotInitializedValidatorShouldThrowErrorTest()
        {
            var validator = GetValidator();
            ShouldThrow<ArgumentException>(() => validator.GetErrors(PropertyToValidate));
        }

        [TestMethod]
        public void DoubleInitializedValidatorShouldThrowErrorTest()
        {
            var validator = GetValidator();
            validator.Initialize(new ValidatorContext(new object(), GetServiceProvider()));
            ShouldThrow<ArgumentException>(() => validator.Initialize(new ValidatorContext(new object(), GetServiceProvider())));
        }

        [TestMethod]
        public virtual void CloneTest()
        {
            var validator = GetValidator();
            validator.Context.ShouldBeNull();
            IValidator clone = validator.Clone();
            validator.ShouldNotEqual(clone);
            clone.Context.ShouldBeNull();

            clone.Initialize(new ValidatorContext(new object(), GetServiceProvider()));
            clone.Context.ShouldNotBeNull();
            IValidator clone2 = clone.Clone();
            clone.ShouldNotEqual(clone2);
            clone2.Context.ShouldBeNull();
        }

        [TestMethod]
        public void SetErrorsTest()
        {
            var validator = GetValidator();
            validator.Initialize(new ValidatorContext(new object(), GetServiceProvider()));
            validator.IsValid().ShouldBeTrue();
            validator.SetErrors(PropertyToValidate, ValidatorError);
            validator.IsValid().ShouldBeFalse();

            string[] errors = validator.GetErrors(PropertyToValidate)
                .OfType<string>()
                .ToArray();
            errors.Length.ShouldEqual(1);
            errors.Contains(ValidatorError).ShouldBeTrue();

            validator.SetErrors(PropertyToValidate);
            validator.GetErrors(PropertyToValidate).ShouldBeEmpty();
            validator.IsValid().ShouldBeTrue();
        }

#if !NODATAERROR
        [TestMethod]
        public void DataErrorTest()
        {
            var validator = GetValidator();
            validator.Initialize(new ValidatorContext(new object(), GetServiceProvider()));
            validator.IsValid().ShouldBeTrue();

            validator.SetErrors(PropertyToValidate, ValidatorError);
            string error = validator[PropertyToValidate];
            if (validator is SimpleValidator)
            {
                error.ShouldEqual(ValidatorError);
                validator.IsValid().ShouldBeFalse();
            }
            else
            {
                error.ShouldBeNull();
                validator.IsValid().ShouldBeTrue();
            }
            validator.SetErrors(PropertyToValidate);
            error = validator[PropertyToValidate];
            error.ShouldBeNull();
            validator.IsValid().ShouldBeTrue();
        }
#endif

        [TestMethod]
        public void IgnorePropertiesTest()
        {
            var validator = GetValidator();
            validator.Initialize(new ValidatorContext(new object(), null, new List<string> { PropertyToValidate }, null,
                GetServiceProvider()));
            validator.IsValid().ShouldBeTrue();
            validator.SetErrors(PropertyToValidate, ValidatorError);
            validator.IsValid().ShouldBeTrue();
            validator.GetErrors(PropertyToValidate).ShouldBeEmpty();
        }

        [TestMethod]
        public void ErrorNameMappingTest()
        {
            var validator = GetValidator();
            var dictionary = new Dictionary<string, ICollection<string>>
                             {
                                 {
                                     PropertyToValidate,
                                     new List<string> {PropertyToMap}
                                 }
                             };
            validator.Initialize(new ValidatorContext(new object(), dictionary, null, null, GetServiceProvider()));
            validator.IsValid().ShouldBeTrue();
            validator.SetErrors(PropertyToValidate, ValidatorError);
            validator.IsValid().ShouldBeFalse();
            validator.GetErrors(PropertyToValidate).ShouldBeEmpty();

            string[] errors = validator.GetErrors(PropertyToMap).OfType<string>().ToArray();
            errors.Length.ShouldEqual(1);
            errors.Contains(ValidatorError).ShouldBeTrue();

            validator.SetErrors(PropertyToMap);
            validator.GetErrors(PropertyToMap).ShouldBeEmpty();
            validator.IsValid().ShouldBeTrue();
        }

        [TestMethod]
        public void NotifyListenersTest()
        {
            var listener = new GenericHandler<DataErrorsChangedMessage>();
            var validator = GetValidator();
            validator.Initialize(new ValidatorContext(new object(), GetServiceProvider()));
            validator.Subscribe(listener);

            validator.IsValid().ShouldBeTrue();
            validator.SetErrors(PropertyToValidate, ValidatorError);
            listener.Count.ShouldEqual(1);
            listener.Sender.ShouldEqual(validator);
            var state = listener.Message;
            state.PropertyName.ShouldEqual(PropertyToValidate);

            validator.IsValid().ShouldBeFalse();
        }

#if !NONOTIFYDATAERROR

        [TestMethod]
        public void NotifyDataErrorInfoTest()
        {
            var validator = GetValidator();
            INotifyDataErrorInfo notifyDataError = validator;

            validator.Initialize(new ValidatorContext(new object(), GetServiceProvider()));
            notifyDataError.HasErrors.ShouldBeFalse();
            validator.SetErrors(PropertyToValidate, ValidatorError);
            notifyDataError.HasErrors.ShouldBeTrue();

            string[] errors = notifyDataError.GetErrors(PropertyToValidate).OfType<string>().ToArray();
            errors.Length.ShouldEqual(1);
            errors.Contains(ValidatorError).ShouldBeTrue();

            validator.SetErrors(PropertyToValidate);
            notifyDataError.GetErrors(PropertyToValidate).ShouldBeEmpty();
            notifyDataError.HasErrors.ShouldBeFalse();
        }

        [TestMethod]
        public void OnPropertyChangedShouldCallValidateMethodTest()
        {
            var modelToValidate = new TestValidatableModel();
            var validator = new SpyValidator();
            validator.Initialize(new ValidatorContext(modelToValidate, GetServiceProvider()));

            validator.ValidateCount.ShouldEqual(0);
            modelToValidate.OnPropertyChanged(PropertyToValidate, ExecutionType.None);
            validator.ValidateCount.ShouldEqual(1);
            validator.ValidateProperties.ShouldContain(PropertyToValidate);
            validator.ValidateAllCount.ShouldEqual(0);
        }

        [TestMethod]
        public void ValidateMethodShouldCallNotifyEventTest()
        {
            int countInvoke = 0;
            var validator = GetValidator();
            INotifyDataErrorInfo notifyDataError = validator;
            notifyDataError.ErrorsChanged += (sender, args) =>
                                                 {
                                                     args.PropertyName.ShouldEqual(PropertyToValidate);
                                                     countInvoke++;
                                                 };

            validator.Initialize(new ValidatorContext(new object(), GetServiceProvider()));
            notifyDataError.HasErrors.ShouldBeFalse();
            validator.SetErrors(PropertyToValidate, ValidatorError);
            countInvoke.ShouldEqual(1);
            notifyDataError.HasErrors.ShouldBeTrue();

            validator.Validate(PropertyToValidate);
            countInvoke.ShouldEqual(2);
        }

#endif

        #endregion

        #region Methods

        protected virtual IServiceProvider GetServiceProvider()
        {
            return null;
        }

        protected virtual ValidatorBase GetValidator()
        {
            return new SimpleValidator();
        }

        #endregion

    }
}